using System;

using Nextem.String;

namespace Renraku.Compiler {
	public enum Op {
		| Nil
		
		| @+
		| @-
		| @*
		| @/
		| @**
		| @%
		
		| Cmp
		| @!
		| @==
		| @!=
		| @<
		| @>
		| @<=
		| @>=
		
		| @|
		| @&
		| @^
		| @~
	}
	
	public variant Inst [ArgT, LabelT] {
		| Breakpoint { /* XXX: I'm assuming nothing goes here */ }
		| Push {
			Src : ArgT
		}
		| Pop {
			Dest : ArgT
		}
		| Return {
			Val : ArgT
		}
		
		| Assign {
			Dest : ArgT;
			Src : ArgT
		}
		| LoadAddr {
			Dest : ArgT;
			Src : ArgT
		}
		| LoadElem {
			Size : int;
			Dest : ArgT;
			Array : ArgT;
			Index : ArgT;
		}
		| StoreElem {
			Size : int;
			Array : ArgT;
			Index : ArgT;
			Value : ArgT
		}
		| StoreInd {
			Size : int;
			Dest : ArgT;
			Src : ArgT
		}
		| Conv {
			Ovf : bool;
			Size : int;
			Dest : ArgT;
			Src : ArgT
		}
		| Unary {
			Op : Op;
			Dest : ArgT;
			Src : ArgT
		}
		| Binary {
			Op : Op;
			Ovf : bool;
			Dest : ArgT;
			Opers : ArgT * ArgT
		}
		
		| Branch {
			Comp : Op * ArgT * ArgT;
			Unsigned : bool;
			mutable Taken : LabelT;
			mutable NotTaken : LabelT
		}
		
		public override ToString() : string {
			match(this) {
				| Assign(dest, src) => "{0} = {1}" <- (dest, src)
				| LoadAddr(dest, src) => "{0} = & {1}" <- (dest, src)
				| LoadElem(size, dest, arr, index) =>
					"{0} ={1} {2}[{3}]" <- (dest, size, arr, index)
				| StoreElem(size, arr, index, value) =>
					"{0}[{1}] ={2} {3}" <- (arr, index, size, value)
				| StoreInd(size, dest, src) => "*{0} ={1} {2}" <- (dest, size, src)
				| Conv(ovf, size, dest, src) =>
					"{0} = {1} :>{2} {3}" <- (dest, src, if(ovf) ".ovf" else "", size)
				| Push(arg) => "push({0})" <- arg
				| Pop(arg)  => "{0} = pop()" <- arg
				| Unary(op, dest, src) =>
					"{0} = {1} {2}" <- (dest, op, src)
				| Binary(op, ovf, dest, (a, b)) =>
					"{0} = {1} {2}{3} {4}" <- (dest, a, op, if(ovf) ".ovf" else "", b)
				| Branch((op, _, _) as comp, _, taken, not) =>
					if(op == Op.Nil)
						"branch({0})" <- taken
					else
						"branch({0}, {1}, {2})" <- (comp, taken, not)
				| Return(val) => "return({0})" <- val
				| Breakpoint => "break"
			}
		}
	}
	
	public variant Assembly [MBodyT] {
		| Top {
			Children_ : list [Assembly [MBodyT]]
		}
		| Class {
			Name : string;
			Children_ : list [Assembly [MBodyT]]
		}
		| Field {
			Name : string;
			Type : string
		}
		| Property {
			Name : string;
			Type : string;
			Getter : MBodyT;
			Setter : MBodyT
		}
		| Method {
			Name : string;
			Type : list [string] * string;
			Body : MBodyT
		}
		| TypeRef {
			Name : string
		}
		
		public Children : list [Assembly [MBodyT]] {
			get {
				match(this) {
					| Top as obj       => obj.Children_
					| Class as obj     => obj.Children_
					| _ => null
				}
			}
		}
		
		public override ToString() : string {
			def iterChild(list, accum : string = null) {
				match(list) {
					| null => ""
					| [] => accum
					| head :: tail =>
						iterChild(
							tail, 
							if(accum == null)
								head.ToString()
							else
								"{0}, {1}" <- (accum, head.ToString())
						)
				}
			}
			
			match(this) {
				| TypeRef(name) => "typeref:{0}" <- name
				| _ => 
					def str = 
						match(this) {
							| Top => "top"
							| Class(name, _) => "class:{0}" <- name
							| Method(name, _, body) => "method:{0} {1}" <- (name, body)
							| _ => this.GetType().ToString()
						}
					if(Children == null)
						"({0})" <- str
					else
						"({0} ({1}))" <- (
								str, 
								iterChild(Children)
							)
			}
		}
		
		public MapMethods [DBodyT](func : MBodyT -> DBodyT) : Assembly [DBodyT] {
			def map = _.Map(_.MapMethods(func));
			match(this) {
				| Top(children) => Top(map(children))
				| Class(name, children) => Class(name, map(children))
				| Field(name, typ) => Field(name, typ)
				| Property(name, typ, getter, setter) => Property(name, typ, func(getter), func(setter))
				| Method(name, typ, body) => Method(name, typ, func(body))
				| TypeRef(typ) => TypeRef(typ)
			}
		}
	}
	
	[Record]
	public class GraphBody [ArgT] {
		public Insts : list [Inst [ArgT, GraphBody [ArgT]]];
	}
}
